/**
 * Copyright 2009 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * 	http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.xume.raob.extensions;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import org.xume.raob.util.ArrayUtils;

/**
 * @author Johan Siebens
 */
public class ExtensionMethod {

	private Method builderMethod;

	private Object extension;

	private Method extensionMethod;

	public ExtensionMethod(Method builderMethod, Object extension) throws ExtensionException {
		this.builderMethod = builderMethod;
		this.extension = extension;
		this.lookUpExtensionMethod();
	}

	public Object execute(Object... args) {
		try {
			return extensionMethod.invoke(extension, args);
		} catch (ExtensionException e) {
			throw e;
		} catch (InvocationTargetException e) {
			throw translateInvocationTargetException(e);
		} catch (Exception e) {
			throw new ExtensionException("Failed to invoke method [" + extensionMethod + "]", e);
		}
	}

	public boolean matches(Method method) {
		return this.builderMethod.equals(method);
	}

	private void lookUpExtensionMethod() {
		Class<?>[] parameterTypes = builderMethod.getParameterTypes();
		Class<?>[] actualParameterTypes = (Class<?>[]) ArrayUtils.addFirst(parameterTypes, Object.class);
		try {
			extensionMethod = extension.getClass().getDeclaredMethod(builderMethod.getName(), actualParameterTypes);
		} catch (NoSuchMethodException e) {
			throw new ExtensionException("No match found for method [" + builderMethod + "] in extension class [" + extension.getClass()
					+ "]", e);
		}
	}

	private ExtensionException translateInvocationTargetException(InvocationTargetException e) {
		Throwable targetException = e.getTargetException();
		if (targetException instanceof ExtensionException) {
			return (ExtensionException) targetException;
		} else {
			return new ExtensionException("Failed to invoke method [" + extensionMethod + "]", e);
		}
	}

}
